package de.dailab.graphworld.gui;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;

import de.dailab.graphworld.ontology.Edge;
import de.dailab.graphworld.ontology.Graph;
import de.dailab.graphworld.ontology.Node;
import de.dailab.graphworld.ontology.Team;

public class GraphWorldGUI extends JFrame{
	
	private static final long serialVersionUID = 3236638840292750364L;
	
	private final int size = 16;
	
	protected Graph graph;
	private JMenuBar menuBar;
	private DrawPanel drawPanel;
	private JTextPane nodeList;
	private JLabel score;
	public int collected;
	
	private Map<String, Team> teams;
	private Map<String, AtomicInteger> scores = new HashMap<String, AtomicInteger>();
	
	StyleContext sc;
	DefaultStyledDocument doc;
	final Style style;
	
	public GraphWorldGUI(boolean editormode){
		
		// main frame
		this.setTitle("GraphWorld - AOT Lesson 3");
		this.getContentPane().setLayout(new BorderLayout());
		this.setSize(645,550);
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		// text area
		sc = new StyleContext();
		doc = new DefaultStyledDocument(sc);
		style = sc.getStyle(StyleContext.DEFAULT_STYLE);
		nodeList = new JTextPane(doc);
		nodeList.setBackground(new Color(239,239,239));
		nodeList.setEditable(false);
		StyleConstants.setForeground(style, Color.BLACK);
		StyleConstants.setBold(style, true);
		try { doc.insertString(doc.getLength(), "Nodes:                                 \n\n\nEdges:\n\n", style);	}
        catch (BadLocationException e){}
		StyleConstants.setBold(style, false);
		
		// gold counter
		collected = 0;
		score = new JLabel();
		score.setText("Gold collected: " + collected);
				
		// menu
		menuBar = new GraphWorldMenuBar(this, editormode);
				
		// graph draw area
		drawPanel = new DrawPanel();
		
		// add everything and make it visible
		this.getRootPane().setDoubleBuffered(true);
		JScrollPane drawScroll = new JScrollPane(drawPanel);
		drawScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
		drawScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		this.getContentPane().add(drawScroll ,BorderLayout.CENTER);
		this.getContentPane().add(menuBar,BorderLayout.NORTH);
//		this.getContentPane().add(new JScrollPane(nodeList),BorderLayout.EAST);
		if(!editormode)
			this.getContentPane().add(score,BorderLayout.SOUTH);
		this.setVisible(true);
	}
	
	/**
	 * Loads a graph into the interface 
	 * @param g - the graph to load and draw
	 */
	public void setGraph (Graph g){
		graph = g;
		update();
	}
	
	/**
	 * Returns the map of agent team membership
	 * @return map
	 */
	public Map<String, Team> getTeams() {
		return teams;
	}

	/**
	 * Sets the map of agent team membership
	 * @param teams map
	 */
	public void setTeams(Map<String, Team> teams) {
		this.teams = teams;
	}
	
	/**
	 * Notifies the GUI that a new team has been added.
	 * @param teamName name of the team
	 */
	public void teamAdded(String teamName) {
		if (!scores.containsKey(teamName)) {
			scores.put(teamName, new AtomicInteger(0));
		}
	}
	
	/**
	 * Updates graphics and textarea with nodes/edges
	 */
	public void update(){
		updateScore();
//		updateTextPane();
		repaint();
	}
	
	@SuppressWarnings("unused")
	private void updateTextPane() {
		
		// clear text area
		try { doc.remove(0, doc.getLength());} 
		catch (BadLocationException e1) {}
		
		// set new old title
		StyleConstants.setForeground(style, Color.BLACK);
		StyleConstants.setBold(style, true);
		try { doc.insertString(doc.getLength(), "Nodes:                                 \n\n", style);	}
        catch (BadLocationException e){}
        StyleConstants.setBold(style, false);
		
        // add nodes
        if(graph!=null && graph.getNodes()!=null){
			for(Node n : graph.getNodes()){
				if(n.getGold()>0){
				    StyleConstants.setForeground(style, new Color(207,181,43));
				    // NODE ID + AMOUNT OF GOLD
				    try { doc.insertString(doc.getLength(), "ID: " + n.getID() + " - Gold: " + n.getGold()+"\n", style); }
			        catch (BadLocationException e){}
				} else {
					StyleConstants.setForeground(style, Color.BLACK);
					// NODE ID + AMOUNT OF GOLD
					try { doc.insertString(doc.getLength(), "ID: " + n.getID() + " - Gold: 0\n", style); }
			        catch (BadLocationException e){}
				}
			}
		
			// done with nodes, prepare for edges
			StyleConstants.setForeground(style, Color.BLACK);
			StyleConstants.setBold(style, true);
			try { doc.insertString(doc.getLength(), "\nEdges:\n\n", style); }
	    	catch (BadLocationException e){}
			StyleConstants.setBold(style, false);
			
			// add edges
			for(Node n : graph.getNodes()){
				for(Edge e : n.getEdges()){
					if(e.getTarget().getID() > n.getID()){
						try { doc.insertString(doc.getLength(), "" + n.getID() + " \u2194 " + e.getTarget().getID() + " - weight: " + e.getWeight()+"\n", style); }
				        catch (BadLocationException ex){}
					}
				}
			}
        }
		// scroll to the top
		nodeList.setCaretPosition(0);
	}
	
	/**
	 * Returns the color for an agent specified by an agent id.
	 * @param agentId agent uuid
	 * @return AWT Color according to the agent's team membership
	 */
	private Color getAgentColor(String agentId) {
		if (this.teams != null && this.teams.containsKey(agentId)) {
			return teams.get(agentId).getTeamColor();
		}
		return Color.RED;
	}
	
	
	class DrawPanel extends JComponent {

		static final long serialVersionUID = -4567754128064495247L;
		
		private final Stroke wideStroke = new BasicStroke(2f);
		private final Stroke normalStroke = new BasicStroke(1f);
		
		Dimension preferredSize = new Dimension(0,0);
				
		@Override
		public Dimension getPreferredSize() {
			return preferredSize;
		}

		
		
		@Override
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			int maxX = 0;
			int maxY = 0;
			
				
			if(graph != null){
				for (Node n : graph.getNodes()){
					// check for node positions to add scrollbars if needed
					if(n.getX() > maxX)
						maxX = n.getX();
					if(n.getY() > maxY)
						maxY = n.getY();
					// draw edges
					for(Edge e : n.getEdges()){
						if(e.getTarget().getID() > n.getID()){
							double pheromone = e.getPheromone().getGoldPheromone() + e.getPheromone().getHomePheromone();
							
							for (Edge back: e.getTarget().getEdges()) {
								// get the reverse edge and add its values
								if (back.getTarget().getID() == n.getID()) {
									pheromone += back.getPheromone().getGoldPheromone() + back.getPheromone().getHomePheromone();
									break;
								}
							}
							
							// check for pheromones
							if(pheromone > 0.01) {
								int strength = (int) (255 * Math.min(pheromone, 1));
								((Graphics2D)g).setStroke(wideStroke);
								g.setColor(new Color(0, 0, strength, 255));
							} else {
								((Graphics2D)g).setStroke(normalStroke);
								g.setColor(Color.BLACK);
							}
							g.drawLine(n.getX()+16, n.getY()+16, e.getTarget().getX()+16, e.getTarget().getY()+16);
						}
					}
					// draw nodes
					if(n.getGold()>0 && n.getID() != 0) // GOLD
						g.setColor(new Color(207,181,43));
					else if(n.getID()==0) // HOMEBASE
						g.setColor(new Color(0x1E90FF));
					else
						g.setColor(Color.GRAY); // EMPTY
					g.fillOval(n.getX()+8, n.getY()+8, size, size);
					g.setColor(Color.BLACK);
					g.drawOval(n.getX()+8, n.getY()+8, size, size);
					if(n.getID()<10)
						g.drawString(" "+n.getID(), n.getX()+9, n.getY()+21);
					else
						g.drawString(""+n.getID(), n.getX()+9, n.getY()+21);
					// draw agents
					if(n.agents != null && !n.agents.isEmpty()){
						g.setColor(GraphWorldGUI.this.getAgentColor(n.agents.get(0).getAid())); // AGENTS
						g.fillOval(n.getX()+12, n.getY()+12, size/2, size/2);
					}
				}
				preferredSize = new Dimension(maxX+50,maxY+50);
			}
		}
	}
	
	protected void save(){
		if(graph != null){
			JFileChooser fc = new JFileChooser();
			int retVar = fc.showSaveDialog(this);
			if(retVar == JFileChooser.APPROVE_OPTION){
				try {
		            BufferedWriter bw = new BufferedWriter(new FileWriter(fc.getSelectedFile()));
		            bw.write(""+graph.getNodes().size()+"\n");
		            for (Node n : graph.getNodes()) {
		                bw.write(""+n.getX()+" "+n.getY()+" "+n.getGold()+"\n");
		            }
		            for (Node n : graph.getNodes()){
		            	for (Edge e : n.getEdges()){
		                	bw.write(""+n.getID()+" "+e.getWeight()+" "+e.getTarget().getID()+" ");
		                }
		            }
		            bw.flush();
		            bw.close();
		        } catch (IOException e) {
		            System.err.println(e);
		        }
		        JOptionPane.showMessageDialog(this, "Graph saved to " + fc.getSelectedFile().getName(), "Saved", JOptionPane.INFORMATION_MESSAGE);
			}
		} else {
			JOptionPane.showMessageDialog(this,"There is no graph to save.","Source Error",JOptionPane.ERROR_MESSAGE);
		}
	}
	
	protected void load(File file){
		
		String line;
		StringTokenizer tok;
		Graph graph = new Graph();
		try{
			BufferedReader br = new BufferedReader(new FileReader(file));
			int size = Integer.valueOf(br.readLine());
			for(int i=0;i<size;i++){
				tok = new StringTokenizer(br.readLine());
				graph.addNode(Integer.valueOf(tok.nextToken()),Integer.valueOf(tok.nextToken()),Integer.valueOf(tok.nextToken()));
			}
			line = br.readLine();
			tok = new StringTokenizer(line);
			while(tok.hasMoreTokens()){
				((Node)graph.getNodes().get(Integer.parseInt(tok.nextToken()))).addEdge(Integer.parseInt(tok.nextToken()), (Node)graph.getNodes().get(Integer.parseInt(tok.nextToken())));
			}
			
			setGraph(graph);
			JOptionPane.showMessageDialog(this, "Graph loaded from "+file,"Loaded",JOptionPane.INFORMATION_MESSAGE);
			br.close();
		} catch (Exception e){
			JOptionPane.showMessageDialog(this,"Loading failed.","Corrupt file",JOptionPane.ERROR_MESSAGE);
		}
	}
	
	/**
	 * gets node count from the actual graph
	 * @return node count
	 */
	public int getNodeCount(){
		return graph.getNodes().size();
	}
	
	/**
	 * Increases the score of the specified team by one.
	 * @param teamName team name
	 */
	public void increaseTeamScore(String teamName) {
		if (scores.containsKey(teamName)) {
			scores.get(teamName).incrementAndGet();
		} else {
			scores.put(teamName, new AtomicInteger(1));
		}
	}
	
	/**
	 * Increases the score of the specified team by the specified amount.
	 * @param teamName team name
	 * @param byAmount amount to raise the score
	 */
	public void increaseTeamScore(String teamName, int byAmount) {
		if (scores.containsKey(teamName)) {
			scores.get(teamName).addAndGet(byAmount);
		} else {
			scores.put(teamName, new AtomicInteger(byAmount));
		}
	}
	
	/**
	 * Returns the appropriate color for a team with the given name.
	 * @param teamName
	 * @return AWT Color
	 */
	private Color getTeamColor(String teamName) {
		for (Team t : teams.values()) {
			if (t.getTeamName().equals(teamName)) {
				return t.getTeamColor();
			}
		}
		return Color.RED;
	}
	
	/**
	 * Converts AWT color to a hex value that can be used in html content
	 * @param color AWT color
	 * @return hex String
	 */
	private String colorToHex(Color color) {
		String colorString = Integer.toHexString(color.getRGB());
		return colorString.substring(2);
	}
	
	/**
	 * Updates the score display.
	 */
	private void updateScore(){
		if (teams != null && !scores.isEmpty()) {
			StringBuffer sb = new StringBuffer("<html>");
			sb.append("Gold collected:&nbsp&nbsp");
			for (String teamName : scores.keySet()) {
				
				sb.append("<span color=#").append(this.colorToHex(this.getTeamColor(teamName))).append(">");
				sb.append(teamName).append(": ");
				sb.append(String.valueOf(scores.get(teamName)));	
				sb.append("</span>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp");
			}
			sb.append("</html>");
			score.setText(sb.toString());
		} else {
			score.setText("Gold collected: " + collected);
		}
	}
	
}
